﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace ComicsEditor
{
    public class BalloonFillInstrument : Instrument
    {
        public int Threshold = 32;
        public Balloon Balloon = null;

        internal static Point[] Dirs = new Point[] { new Point(1, 0), new Point(1, -1), new Point(0, -1), new Point(-1, -1), new Point(-1, 0), new Point(-1, 1), new Point(0, 1), new Point(1, 1) };

        public BalloonFillInstrument()
        {
        }

        public override bool Activate(Point point, out Point offset)
        {
            Cursor.Current = Cursors.WaitCursor;
            offset = Point.Empty;

            if (Balloon != null && !Balloon.Contains(point))
                return false;

            int i, j, k;
            sbyte[,] map = new sbyte[Page.Height + 2, Page.Width + 2];

            Queue<Point> front = new Queue<Point>();
            front.Enqueue(point);
            map[point.Y + 1, point.X + 1] = 1;
            Color color = (pageInfo.Image as Bitmap).GetPixel(point.X, point.Y);
            Point BoundTL = new Point(Page.Width - 1, Page.Height - 1);
            Point BoundBR = new Point(0, 0);

            while (front.Count > 0)
            {
                Point p = front.Dequeue();
                for (i = 0; i < 8; i++)
                {
                    Point t = p;
                    t.X += Dirs[i].X;
                    t.Y += Dirs[i].Y;
                    if (t.X < 0 || t.X >= Page.Width || t.Y < 0 || t.Y >= Page.Height || map[t.Y + 1, t.X + 1] != 0 ||
                        (Balloon != null && !Balloon.Contains(t)))
                        continue;
                    Color c = (pageInfo.Image as Bitmap).GetPixel(t.X, t.Y);
                    if (Math.Abs(c.R - color.R) < Threshold && Math.Abs(c.G - color.G) < Threshold && Math.Abs(c.B - color.B) < Threshold)
                    {
                        front.Enqueue(t);
                        map[t.Y + 1, t.X + 1] = 1;
                        if (BoundBR.X < t.X)
                            BoundBR.X = t.X;
                        if (BoundTL.X > t.X)
                            BoundTL.X = t.X;
                        if (BoundBR.Y < t.Y)
                            BoundBR.Y = t.Y;
                        if (BoundTL.Y > t.Y)
                            BoundTL.Y = t.Y;
                    }
                    else
                        map[t.Y + 1, t.X + 1] = -1;
                }
            }

            /*BoundTL.X -= 1;
            if (BoundTL.X < 0)
                BoundTL.X = 0;
            BoundTL.Y -= 1;
            if (BoundTL.Y < 0)
                BoundTL.Y = 0;
            BoundBR.X += 1;
            if (BoundBR.X > Page.Width - 1)
                BoundBR.X = Page.Width - 1;
            BoundBR.Y += 1;
            if (BoundBR.Y > Page.Height - 1)
                BoundBR.Y = Page.Height - 1;*/
            BoundBR.X += 2;
            BoundBR.Y += 2;

            front.Enqueue(new Point(BoundTL.X, BoundTL.Y));
            map[BoundTL.Y, BoundTL.X] = -2;
            while (front.Count > 0)
            {
                Point p = front.Dequeue();
                for (i = 0; i < 8; i += 2)
                {
                    Point t = p;
                    t.X += Dirs[i].X;
                    t.Y += Dirs[i].Y;
                    //if (t.X < 0 || t.X >= Page.Width || t.Y < 0 || t.Y >= Page.Height)
                        //continue;
                    if (t.X < BoundTL.X || t.X > BoundBR.X || t.Y < BoundTL.Y || t.Y > BoundBR.Y)
                        continue;
                    if (Math.Abs(map[t.Y, t.X]) == 2)
                        continue;
                    if (map[t.Y, t.X] == 1)
                    {
                        map[t.Y, t.X] = 2;
                    }
                    else
                    {
                        front.Enqueue(t);
                        map[t.Y, t.X] = -2;
                    }
                }
            }

            while (true)
            {
                Point minP = new Point(-1, -1);
                for (i = BoundTL.Y; i <= BoundBR.Y && minP.Y < 0; i++)
                    for (j = BoundTL.X; j <= BoundBR.X && minP.X < 0; j++)
                        if (map[i, j] == 2)
                            minP = new Point(j, i);
                if (minP.X < 0)
                    break;

                List<Point> stack = new List<Point>();
                stack.Add(minP);
                map[minP.Y, minP.X] = 3;
                int lastDir = 0;
                while (true)
                {
                    Point curP = new Point(-1, -1);
                    int curDir = (lastDir + 4)%8;
                    for (i = 0; i < 8; i++)
                    {
                        curDir = (curDir + 1)%8;
                        int tx = stack[stack.Count - 1].X + Dirs[curDir].X;
                        int ty = stack[stack.Count - 1].Y + Dirs[curDir].Y;
                        //if (tx < 0 || tx >= Page.Width || ty < 0 || ty >= Page.Height)
                            //continue;
                        if (tx < BoundTL.X || tx > BoundBR.X || ty < BoundTL.Y || ty > BoundBR.Y)
                            continue;
                        if (map[ty, tx] == 2)
                        {
                            curP = new Point(tx, ty);
                            break;
                        }
                        if (map[ty, tx] == 3)
                        {
                            if (tx == stack[stack.Count - 2].X && ty == stack[stack.Count - 2].Y)
                            {
                                map[stack[stack.Count - 1].Y, stack[stack.Count - 1].X] = -2;
                                stack.RemoveAt(stack.Count - 1);
                                curDir = (curDir + 4)%8;
                                i = 0;
                            }
                            else
                            {
                                for (j = stack.Count - 1; tx != stack[j].X || ty != stack[j].Y; j--) ;
                                Point[] contour = new Point[stack.Count - j];
                                for (k = 0; k < contour.Length; k++)
                                {
                                    contour[k] = stack[j + k];
                                    contour[k].X--;
                                    contour[k].Y--;
                                    if (k > 0)
                                        map[stack[j + k].Y, stack[j + k].X] = -2;
                                }
                                for (curDir = 0; curDir < 8; curDir++)
                                    if (stack[j].X + Dirs[curDir].X == stack[j + 1].X && stack[j].Y + Dirs[curDir].Y == stack[j + 1].Y)
                                        break;
                                stack.RemoveRange(j + 1, stack.Count - j - 1);
                                i = 0;

                                if (contour.Length > 10)
                                {
                                    Balloon balloon = new Balloon(Page, new Contour(contour));
                                    pageInfo.Balloons.Add(balloon);
                                    if (pageInfo.BalloonCreate != null)
                                        pageInfo.BalloonCreate(balloon);
                                }
                            }
                        }
                    }
                    if (curP.X < 0)
                    {
                        map[minP.Y, minP.X] = -2;
                        break;
                    }
                    stack.Add(curP);
                    map[curP.Y, curP.X] = 3;
                    lastDir = curDir;
                }
            }

            Cursor.Current = Cursors.Default;

            return true;
        }
    }
}
